{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "# Chatbot with Collection Schema \n",
    "\n",
    "## Review\n",
    "\n",
    "We extended our chatbot to save semantic memories to a single [user profile](https://docs.langchain.com/oss/python/concepts/memory#profile). \n",
    "\n",
    "We also introduced a library, [Trustcall](https://github.com/hinthornw/trustcall), to update this schema with new information. \n",
    "\n",
    "## Goals\n",
    "\n",
    "Sometimes we want to save memories to a [collection](https://docs.google.com/presentation/d/181mvjlgsnxudQI6S3ritg9sooNyu4AcLLFH1UK0kIuk/edit#slide=id.g30eb3c8cf10_0_200) rather than single profile. \n",
    "\n",
    "Here we'll update our chatbot to [save memories to a collection](https://docs.langchain.com/oss/python/concepts/memory#collection).\n",
    "\n",
    "We'll also show how to use Trustcall to update this collection. \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install -U langchain_openai langgraph trustcall langchain_core"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os, getpass\n",
    "\n",
    "def _set_env(var: str):\n",
    "    # Check if the variable is set in the OS environment\n",
    "    env_value = os.environ.get(var)\n",
    "    if not env_value:\n",
    "        # If not set, prompt the user for input\n",
    "        env_value = getpass.getpass(f\"{var}: \")\n",
    "    \n",
    "    # Set the environment variable for the current process\n",
    "    os.environ[var] = env_value\n",
    "\n",
    "_set_env(\"LANGSMITH_API_KEY\")\n",
    "os.environ[\"LANGSMITH_TRACING\"] = \"true\"\n",
    "os.environ[\"LANGSMITH_PROJECT\"] = \"langchain-academy\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "## Defining a collection schema\n",
    "\n",
    "Instead of storing user information in a fixed profile structure, we'll create a flexible collection schema to store memories about user interactions.\n",
    "\n",
    "Each memory will be stored as a separate entry with a single `content` field for the main information we want to remember\n",
    "\n",
    "This approach allows us to build an open-ended collection of memories that can grow and change as we learn more about the user.\n",
    "\n",
    "We can define a collection schema as a [Pydantic](https://docs.pydantic.dev/latest/) object. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel, Field\n",
    "\n",
    "class Memory(BaseModel):\n",
    "    content: str = Field(description=\"The main content of the memory. For example: User expressed interest in learning about French.\")\n",
    "\n",
    "class MemoryCollection(BaseModel):\n",
    "    memories: list[Memory] = Field(description=\"A list of memories about the user.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "_set_env(\"OPENAI_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "We can used LangChain's chat model  [chat model](https://docs.langchain.com/oss/python/langchain/models) interface's [`with_structured_output`](https://docs.langchain.com/oss/python/langchain/models#structured-outputs) method to enforce structured output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Memory(content=\"User's name is Lance.\"),\n",
       " Memory(content='Lance likes to bike.')]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# Initialize the model\n",
    "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
    "\n",
    "# Bind schema to model\n",
    "model_with_structure = model.with_structured_output(MemoryCollection)\n",
    "\n",
    "# Invoke the model to produce structured output that matches the schema\n",
    "memory_collection = model_with_structure.invoke([HumanMessage(\"My name is Lance. I like to bike.\")])\n",
    "memory_collection.memories"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use `model_dump()` to serialize a Pydantic model instance into a Python dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'content': \"User's name is Lance.\"}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "memory_collection.memories[0].model_dump()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Save dictionary representation of each memory to the store. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import uuid\n",
    "from langgraph.store.memory import InMemoryStore\n",
    "\n",
    "# Initialize the in-memory store\n",
    "in_memory_store = InMemoryStore()\n",
    "\n",
    "# Namespace for the memory to save\n",
    "user_id = \"1\"\n",
    "namespace_for_memory = (user_id, \"memories\")\n",
    "\n",
    "# Save a memory to namespace as key and value\n",
    "key = str(uuid.uuid4())\n",
    "value = memory_collection.memories[0].model_dump()\n",
    "in_memory_store.put(namespace_for_memory, key, value)\n",
    "\n",
    "key = str(uuid.uuid4())\n",
    "value = memory_collection.memories[1].model_dump()\n",
    "in_memory_store.put(namespace_for_memory, key, value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Search for memories in the store. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'value': {'content': \"User's name is Lance.\"}, 'key': 'e1c4e5ab-ab0f-4cbb-822d-f29240a983af', 'namespace': ['1', 'memories'], 'created_at': '2024-10-30T21:43:26.893775+00:00', 'updated_at': '2024-10-30T21:43:26.893779+00:00'}\n",
      "{'value': {'content': 'Lance likes to bike.'}, 'key': 'e132a1ea-6202-43ac-a9a6-3ecf2c1780a8', 'namespace': ['1', 'memories'], 'created_at': '2024-10-30T21:43:26.893833+00:00', 'updated_at': '2024-10-30T21:43:26.893834+00:00'}\n"
     ]
    }
   ],
   "source": [
    "# Search \n",
    "for m in in_memory_store.search(namespace_for_memory):\n",
    "    print(m.dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "## Updating collection schema\n",
    "\n",
    "We discussed the challenges with updating a profile schema in the last lesson. \n",
    "\n",
    "The same applies for collections! \n",
    "\n",
    "We want the ability to update the collection with new memories as well as update existing memories in the collection. \n",
    "\n",
    "Now we'll show that [Trustcall](https://github.com/hinthornw/trustcall) can be also used to update a collection. \n",
    "\n",
    "This enables both addition of new memories as well as [updating existing memories in the collection](https://github.com/hinthornw/trustcall?tab=readme-ov-file#simultanous-updates--insertions\n",
    ").\n",
    "\n",
    "Let's define a new extractor with Trustcall. \n",
    "\n",
    "As before, we provide the schema for each memory, `Memory`.  \n",
    "\n",
    "But, we can supply `enable_inserts=True` to allow the extractor to insert new memories to the collection. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from trustcall import create_extractor\n",
    "\n",
    "# Create the extractor\n",
    "trustcall_extractor = create_extractor(\n",
    "    model,\n",
    "    tools=[Memory],\n",
    "    tool_choice=\"Memory\",\n",
    "    enable_inserts=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import HumanMessage, SystemMessage, AIMessage\n",
    "\n",
    "# Instruction\n",
    "instruction = \"\"\"Extract memories from the following conversation:\"\"\"\n",
    "\n",
    "# Conversation\n",
    "conversation = [HumanMessage(content=\"Hi, I'm Lance.\"), \n",
    "                AIMessage(content=\"Nice to meet you, Lance.\"), \n",
    "                HumanMessage(content=\"This morning I had a nice bike ride in San Francisco.\")]\n",
    "\n",
    "# Invoke the extractor\n",
    "result = trustcall_extractor.invoke({\"messages\": [SystemMessage(content=instruction)] + conversation})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  Memory (call_Pj4kctFlpg9TgcMBfMH33N30)\n",
      " Call ID: call_Pj4kctFlpg9TgcMBfMH33N30\n",
      "  Args:\n",
      "    content: Lance had a nice bike ride in San Francisco this morning.\n"
     ]
    }
   ],
   "source": [
    "# Messages contain the tool calls\n",
    "for m in result[\"messages\"]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='Lance had a nice bike ride in San Francisco this morning.'\n"
     ]
    }
   ],
   "source": [
    "# Responses contain the memories that adhere to the schema\n",
    "for m in result[\"responses\"]: \n",
    "    print(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'id': 'call_Pj4kctFlpg9TgcMBfMH33N30'}\n"
     ]
    }
   ],
   "source": [
    "# Metadata contains the tool call  \n",
    "for m in result[\"response_metadata\"]: \n",
    "    print(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('0',\n",
       "  'Memory',\n",
       "  {'content': 'Lance had a nice bike ride in San Francisco this morning.'})]"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Update the conversation\n",
    "updated_conversation = [AIMessage(content=\"That's great, did you do after?\"), \n",
    "                        HumanMessage(content=\"I went to Tartine and ate a croissant.\"),                        \n",
    "                        AIMessage(content=\"What else is on your mind?\"),\n",
    "                        HumanMessage(content=\"I was thinking about my Japan, and going back this winter!\"),]\n",
    "\n",
    "# Update the instruction\n",
    "system_msg = \"\"\"Update existing memories and create new ones based on the following conversation:\"\"\"\n",
    "\n",
    "# We'll save existing memories, giving them an ID, key (tool name), and value\n",
    "tool_name = \"Memory\"\n",
    "existing_memories = [(str(i), tool_name, memory.model_dump()) for i, memory in enumerate(result[\"responses\"])] if result[\"responses\"] else None\n",
    "existing_memories"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Invoke the extractor with our updated conversation and existing memories\n",
    "result = trustcall_extractor.invoke({\"messages\": updated_conversation, \n",
    "                                     \"existing\": existing_memories})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  Memory (call_vxks0YH1hwUxkghv4f5zdkTr)\n",
      " Call ID: call_vxks0YH1hwUxkghv4f5zdkTr\n",
      "  Args:\n",
      "    content: Lance had a nice bike ride in San Francisco this morning. He went to Tartine and ate a croissant. He was thinking about his trip to Japan and going back this winter!\n",
      "  Memory (call_Y4S3poQgFmDfPy2ExPaMRk8g)\n",
      " Call ID: call_Y4S3poQgFmDfPy2ExPaMRk8g\n",
      "  Args:\n",
      "    content: Lance went to Tartine and ate a croissant. He was thinking about his trip to Japan and going back this winter!\n"
     ]
    }
   ],
   "source": [
    "# Messages from the model indicate two tool calls were made\n",
    "for m in result[\"messages\"]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='Lance had a nice bike ride in San Francisco this morning. He went to Tartine and ate a croissant. He was thinking about his trip to Japan and going back this winter!'\n",
      "content='Lance went to Tartine and ate a croissant. He was thinking about his trip to Japan and going back this winter!'\n"
     ]
    }
   ],
   "source": [
    "# Responses contain the memories that adhere to the schema\n",
    "for m in result[\"responses\"]: \n",
    "    print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tells us that we updated the first memory in the collection by specifying the `json_doc_id`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'id': 'call_vxks0YH1hwUxkghv4f5zdkTr', 'json_doc_id': '0'}\n",
      "{'id': 'call_Y4S3poQgFmDfPy2ExPaMRk8g'}\n"
     ]
    }
   ],
   "source": [
    "# Metadata contains the tool call  \n",
    "for m in result[\"response_metadata\"]: \n",
    "    print(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "LangSmith trace: \n",
    "\n",
    "https://smith.langchain.com/public/ebc1cb01-f021-4794-80c0-c75d6ea90446/r"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Chatbot with collection schema updating\n",
    "\n",
    "Now, let's bring Trustcall into our chatbot to create and update a memory collection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAFNAJYDASIAAhEBAxEB/8QAHQABAAMAAwEBAQAAAAAAAAAAAAUGBwIECAMBCf/EAFUQAAEDBAADAgkFCggMBgMAAAEAAgMEBQYRBxIhEzEUFRciQVFWlNMIFjZh0SMyVFV0gZGVstIzcXN1k6GxsxgkJidCQ1JiY3K0wTQ3V4OEhaOk8P/EABsBAQEAAwEBAQAAAAAAAAAAAAABAgMEBQYH/8QANBEAAgADBQUFBwUBAAAAAAAAAAECAxESFCFRkQQxQVLRM2FxkqEFExUiI2KxU8HC4fAy/9oADAMBAAIRAxEAPwD+qaIiAIiIAutWXOjt+vCquCm2NjtpAz+0qAD6vNOZ1PVT22w7LWy055Kit0fvmP744vU4ec/vaWtAL+1R4HjtDsxWShMhJLpZYGySOJ7y57tuJ+sldFiCDCY8cl+/+ZaJbztfOqy/jig95Z9qfOqy/jig95Z9qfNWy/ieg92Z9ifNWy/ieg92Z9ifR7/QuA+dVl/HFB7yz7U+dVl/HFB7yz7U+atl/E9B7sz7E+atl/E9B7sz7E+j3+gwHzqsv44oPeWfanzqsv44oPeWfanzVsv4noPdmfYnzVsv4noPdmfYn0e/0GAGU2UnQu9AT+Us+1d+nqoauPtIJWTR93PG4OH6QugMWsoOxaKDf5Mz7F0Knh7YZJO2paBlprANNq7Z/i0o9I25muYb/wBF2wdnYOylJL4taEwLGigLbca2118Vqu8nhL5ubwS4tjDGz6Gyx4HRsoAJ6ABwBLQNFrZ9ao4HAw8AiIsCBERAEREAVaz6Z77NBbY3mN92qoqAuaSCI3ncuiOoPZNk0R3HRVlVYzodgyxXA77OhusEkhA3psgdBv8AiHbAk+gAn0LfI7SH/Y8PUq3lkiiZBEyKJjY42NDWsYNBoHcAPQFzRFoIFR8042YZw+yCksd9vBpbtVQioZSw0k9Q5kRfyCSTsmOEbC4EBz+UEg9eivC83fKOF2sebQX/AAOy5cOJDLdFT0lbaraaq0XKLt3HwOtJ8xgbtzuclhaJNh5+9AF6xv5Qdqv/ABryjh46hr4Kq0mnjgqhQVT46iR8ckknO/sezia0MAa5z9P2eUnuUriXH7As4yj5u2a/dveHNkfFTzUc9OKgR/fmF8kbWy8vp5C7Q69ypNsqr1g3yiM7lqMbu1VDltFajbLlRUUlRQxzQRTRyMqJWjUIDnNO3a207CyXCrdll24g8H8gv9o4hV2SW66VAyaqu1PM23UUs9JPCG00IPZ9lzvA7WJpaGAF7+oQG3ZJ8qzBbbimUXaz1dXkNTY6WsllpqS21hYJqcuY6GSVsLmxEvAG3dzTz6LPOV24WcRqHinhdBf6CKqgbNGwTRVVFPTFkpja9zWiZjC9o5wA9oLT6CdFZJw74f3mo+S3n+N+Kp6C+XiXJY4aariMD5Xz1FUIXEOA6Oa6Mhx6FpB7lovAfJ5Mg4b2WnqLFe7BWWyipqKppr3b5KR/asiaHcnOPPaCCOZuwfWgNEREQEJmdsfdcaro4CG1sTPCKSR3+rnj8+N3T0BwGx6Rseld6y3SO92aguMIIirKeOoYD6GvaHD+1cMhujLJYbjcJNllLTyTENGyeVpOgPSTrQHpXwxC1PsWJ2S2y/wlHQwU7tetkbWn+xdG+Tjnhpj+xeBLoiLnIEREAREQBda42+nu1vqaKriE1LUxuiljd3Oa4aI/QV2UVTadUCuWq8yWeeGz3qYNqj5lJWvOmVre5o2egm198z09XN6bDYi+8CeHOUXeput4wXHrpcql3PPV1dthkllOgNucW7J0AOvqVyr7fS3WjlpK2mhq6WVvLJBOwPY8eotPQqA+YVNT9KC63i2x9dRQVznsbv1Nk5w0fUND6lv+nMxbo/T+i4Mrv+DXwn/9N8WP/wBRB+6rrjWL2fDbPDabDa6SzWyEuMdHQwthiYXEudprQANkkn6yov5k1HtVfv6aH4SfMmo9qr9/TQ/CT3cvn9GKLMtCKr/Mmo9qr9/TQ/CVTx633W58QMus02U3jwK1sonU5ZLDz7lY9z+Y9n62jXQJ7uXz+jFFmaoq1mPDTEuIZpDlGNWrITSc/g5udHHP2PNrm5eYHW+Vu9d+h6lw+ZNR7VX7+mh+EnzJqPaq/f00Pwk93L5/RiizK/8A4NnCfWvJvi2vV4pg1+yp/E+GmG8N/DKjHMbs+N9uweEy2+kjp+drdkc5aBsDZPXu2V+jCagEH503469Bmh6//iXKPh9a5JGSXGSsvbmEFrbnUvmjBB2D2RPZ7313y7SxKW+PRdaCiPmZW53UU5g0/HaeVsxn66rZGEOYGegxBwDubucQNbGybUnci1xx2qJYJBsIiLWQIiIAiIgCIiAIiIAiIgCz3DiPLBxFGzvsrZsf+1J9a0JZ7h2/LBxF7tdlbO7W/wCCk/P+lAaEiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAs8w3/zi4jdQfuVr6AdR9ylWhrPMN15YuI3r7K1+j/hSoDQ0REAREQBERAEREAREQBERAEUTkOQMsNNERC6rrKh/ZU1Kw6Mr9E9SejWgAkuPcB6SQDXzfswcdi3WRm/9Hw2Z2vq32Q3+gLogkRzFaW7vdC0LsipHj3MPwCx+9zfDTx7mH4BY/e5vhrZdY81qhQu6KkePcw/ALH73N8NPHuYfgFj97m+Gl1jzWqFC0X+trLbYrlV2+h8aXCnppJaeh7XsvCJGtJZHz6PLzEAb0db3orw5wI+XVV8SPlB1FkoeG88VZk1RS00wN1BNBHA14llcOwHPytLncux97rfXa9d+Pcw/ALH73N8NZBw8+T/Nw34y5hxFttvszrnkIGqZ1RKI6QuPNMWfc/8AWOAPo11A6FLrHmtUKHpZFSPHuYfgFj97m+Gnj3MPwCx+9zfDS6x5rVChd0VI8e5h+AWP3ub4aePcw/ALH73N8NLrHmtUKF3RUjx7mH4BY/e5vhrkzJMppj2lTaLbUwt6vjo6x4lI9PIHxhpPqBLQfWEuszNaoULqi6tsuVNebdT11JJ2tNUMEkbtFpIPrB6g+sHqD0K7S5GmnRkCIigCIiApeYH/ACzxYejs6w/n5Y/tKkVHZh9NMW/kqz9mNSK9RdlL8P5Mr4BERQgRQ+S5dacQjt0l2q/BGXCugttMeze/tKiZ3LGzzQdbPTZ0B6SEteXWm9X+92Sjq+2udldC2vg7N7exMrO0j84gNdtvXzSdenRUBMIiKgIiIAih7bl1pu+R3mw0lX2t1s7YH11P2b29iJmudF5xAa7Ya4+aTrXXS55XlNrwjG7jfr1VeBWm3QuqKqo7N0nZxt6k8rAXH+IAlQEqi4xStniZIw8zHtDmn1grkqDr8LzvDYPqqqwDXoAqpQFa1U+F30Nh/K6z/qpVbFy7T28zxf5K97CIi5iBERAUvMPppi38lWfsxqRUdmH00xb+SrP2Y1Ir1F2Uvw/kyvgZFx9vt0juGAYtb7vU47S5Re/AK27UbgyeOJkEsvZRPI8x8jowwOHUdddVBcSrFHg+PWHDrbes3vt5yC7ONvhZkb4ah3ZwOfKySteC+OBrWl51zPLtAbHRa7mmDWHiJYZLNkdtiuluke2TspSWlr2nbXsc0hzHD0OaQR61Vf8AB4wDxCyz+JZjSsrPGDJjcqo1Tajk7PtBU9r2oPIA3o/qOnctbTIYFQX3ILlgNjtuS1U1XW2Di5QWlklTWeGStiZPE9rHzljDKW9qW85aCQBsLZeF5/z/AHGsentrOdf/AAQp2P5P3D6HGbtj0eNwR2W6zx1VXSNmlDXzsDQ2Zp59sk8xpL2kOcRsknqu3duGtPSXuoybFobfasxlo47c6518U9TFJTtcCGyRMmj7RwDdNeTzD16JBiTQOfG24VVp4M57XUNTNR1tNYK+aCpp5CySKRtPIWva4dWuBAII6ghYfQYrcqjiFwytk2c5jJRZTjlXcLtGL3M3tp4m0zmujLSDB1ndsQ8gPKB3b3sJxLNshgqrTlt5xi641cKeakuFFQWaqpZpoZI3Mc1sprX8m+bv5Sdb1o9RY4sAsMN2sNzZQarrFRyUFul7aT7hBIIw9mubTtiKPq4Ejl6Hqd1qoPNeHZPkueS8O8FueU3iloJ67Ioay60lWYK+4Nt9T2VPEZ26c08rg55bpzuTv71xGb5MZXcN48rubKB+fOxoZS6YOrm0YoxVGnE5H8Pz7hEh24D6+q3q4cC8HumPR2Sosm6CKvmukXZ1U8c0NTK9z5ZI5mvEjC5z3/euA0da10XKXgdgs2BjDH45THHBL4R4LzvD+25ubtu15u07TfXtObm+tY2WDzLlFZeeD2W8UrZjd4uVVVXO54vaRdLxci+emiqGzB5NS9khZ080SOa8s5wdHQCmOImG8RMU4QcVfHtQRis2Lz8lHWZJNe6llYCNPbLLTxOaxzC7bSXDbWkAbK3q0/J+wCzWrILbDjzJ6LII4orpFXVM1V4UIgezLjK9x23mOnAg70d7A12ca4JYXidovNst9m5qK8QCmr466qnrDURBrmiNzpnvdygPeA0HQ5illgt1pIdaqMg7BhZoj/lC7ajMZxu3YfYKGy2mB1NbaGIQ08LpXyljB3DmeS46+slSa2A63C76Gw/ldZ/1UqtiqfC76Gw/ldZ/1Uqti5tp7eZ4v8le9hERcxAiIgKXmH00xb+SrP2Y1Ir6ZXYqi5+BVtCY/GFA9z4o5nFrJmuaWujcR3bGiDo6IHQjooE3S/tOjh1ycR3mOqoy3825gf6gvUltRy4UmsFxaXFvj4mW8mkUJ42v3sZdfeqL46eNr97GXX3qi+Os7H3LzLqKE2ihPG1+9jLr71RfHTxtfvYy6+9UXx0sfcvMuooTaKE8bX72MuvvVF8dR1Fm9fcbzcrTT4pdZK+3CJ1VF29IOzEgJZ1M2jsNPcTrXVLH3LzLqKFsRQnja/exl196ovjp42v3sZdfeqL46WPuXmXUUJtFCeNr97GXX3qi+Onja/exl196ovjpY+5eZdRQm0UJ42v3sZdfeqL465NrMjrPuUOMVFFI7oJq+qp+yZ/vERyPcdeoDr6x3pY+5eZdSUJDhd9DYfyus/6qVWxRuO2VmPWWlt7JHTdi080r+he8kuc76tuJOvrUkvOnRKObFGtzb/Ie8IiLSQIiIAiIgCIiAIiIAs+w8f53uIh1/qrZ11/wpPq/7laCs9w5uuMHEU6PWK2dddD9ykQGhIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCIiALPMNI8sXEbr17K176f8KX0rQ1nuHc3lf4i7LuXsrZoEdP4KTuQGhIiIAiIgCIiAIiIAiIgCIoW8Ztj2P1QprnfLdb6kjm7GpqmMfr18pO9LOGCKN0hVWWlSaRVbypYd7U2j32P7U8qWHe1No99j+1bbvO5Hoy2XkWlFVvKlh3tTaPfY/tTypYd7U2j32P7Uu87kejFl5FpRVbypYd7U2j32P7U8qWHe1No99j+1LvO5HoxZeRYq6uprXQ1FbW1EVJR08bppqid4ZHExo25znHoAACST0ACxrBeK2D1nGTNmU+Y2CeS4eK4KRsd0gcamTs5G8ken+e7mIGgN7IHpV6ufEDBLzbau312RWWqoquF8E8ElZGWyRuBa5pG+4gkLwD8mr5NuP4H8q2+3O93q2uxHFpfCrJVz1UfJWyP605ad6Jjbtztfeva0HvS7zuR6MWXkf0wRVbypYd7U2j32P7U8qWHe1No99j+1LvO5HoxZeRaUVW8qWHe1No99j+1PKlh3tTaPfY/tS7zuR6MWXkWlFVvKlh3tTaPfY/tTypYd7U2j32P7Uu87kejFl5FpRVbypYd7U2j32P7VMWbIrVkUT5bVcqS5RxkB7qSdsgaT3A8pOvzrGKTMgVYoWl4Eo0SKIi0kOleqx1vs9dVMAL4IJJWg+trSR/YqjiVJHTWCikA5p6mJk88zur5pHNBc9xPUkk/m7u4Kz5V9GLx+RzfsFV7Gvo5avySL9gL0JGEp+JeBJIiLMgREQBERAEREAREQBERAEREAUFfC223ix3KAdnVOroqN729DJFIeUsd6xshw3vRaNKdUDlv31i/nek/vAtsrGKmZVvNBREXjkIvKvoxePyOb9gqvY19HLV+SRfsBWHKvoxePyOb9gqvY19HLV+SRfsBejJ7F+P7F4HZuc9TS22rmo6ZtZWRwvfDTOk7MSvDSWsLtHl2dDejrfcVkuN/KbsGQVfDKjdTSUtZm1HLUNjL+YUEsbesMh5RsmRssYPTboz09C2NeeKr5J0YsHFCnoroILpkFd4bYasOcPFBZIaqFjSBtgFXLO48ne149PRHXgQkY/lK3K7T4lDYsL8auyqtu1Na3OughY+CjcGtqZCYjyskHM7Q2QA3XOXALo5PxHy225nnUXil9uv9twaK6UlOy/me37L5OZ/ZGmAbKyRsrec752xsBDebpb6fg1JZMn4TS2iSmjseF2+soJI5XOE0gkp4ooywBpBO4yXbI7/Svvf+FFZkPFDJL5NVU8Nnu+IMxzTC41DJe3qHufy8vLy8szdedvYPQd6x+YFNtvyhLng/A7D8gza32+K93qOkprc116jjirnPpmyGonmkjjZTggPc4aeB0ALi4BW7gtx3oeL1bfrY2Ggp7tZuwfUC03aK50kkcodyOjqIwATuN4LS1pBA6aIKpbOCnEGswTC6OrrMap8mwWandZKmEzy0tdEyB0EjKprmNMfaRkfec3Ke5aDa8pu+CWSSsz6joaepqans6eDD7bXXFrIwwHUhZCXk7DzzFjW9QO/qSrxBLcW+ITeFXDy8ZU+3vujLc2N5pI5RG6QOlYzQcQQCObfXv1rY71QZON+dtzOtxJvDKB9/htrbzFGMiZ2ElKXuj0ZOw22bnbyhga5veecDRP24lXOj4/cPMgwvGjcKW7VsEb45b3ZLhQUwDJo3HcstOBvQ6AbJ9WgSLYzA7g3jpNmhmpvFb8bjs4h5ndv2zap8pdrl5eTlcBvm3v0elV1bwBnl7+V5YYLNh9RaoLc64ZHaxeGQZBe4LTBS0++Xz5pA7meX8zQ1jXb5HE6A2vraPlSHLqTEmYzjDLxdb9WXC3GnN2ibBTVFI0OfuoY17ZIi0lwkZvY5dNJOhB4f8nfNeG1twS52Kqx2vyWz4/8AN260F0dN4DVQiZ0zHxStjL2PY9zu9hBDiOmtrQpOHeS3fMOGWQ3SSyxVOPG4vucVuEjI3meAxxiBrgSddNlxb3Ej1LH5gVSg+U3ezbJrxdMANtsdtvox28VTbwyaSkqjUNg544xGO1iD5IwXEsd5x0062ZHJPlGV9rlyy5WrC571h2J1T6O83ltwZFK18Qa6oMFOWkyiIO84l7NlrgN6XVuvAi/13DLOccjrLaK2+5h84KaR0snZsp/GEFTyvPJsP5InDQBGyOuuo6uS8EM6NFnuKY7dLDBh2aV1RWVdXXCbw+gFU0CrZFG1vZyh3nlpc5nLzne9BPmBZqTjTfcl4jZDi+L4hBdqWy+Ayz3equ3g0L4qmFsrS1ohe4vAJ83uIbsuaSAf2ycfo8gx3h3WUdkc67ZZcHW+W1PqdOt7oWyGsc53J5/YmFzdabzEt+92pThvwxqcEzrNrmJKc2m7ttsVBEx7nSxspqUQESbaACSOmidjv13LNeBeK0eQ8ds8ze01UtbhsMr22QuhcyHwyqbE64yQlwHMC+CMcw2NvkAPeriD0goHLfvrF/O9J/eBTygct++sX870n94F0yv+0VbzQURF45CLyr6MXj8jm/YKr2NfRy1fkkX7AVpvNG642iupGEB88EkQJ9Bc0j/uqhiVZHUWGjhB5KmmhZBUQO6Phka0BzHA9QQf0jRHQhehIxlNd5eBMIiLMgREQBERAEREAREQBERAEREAUDlv31i/nek/vAp5QV75bpebJbaciWqZXRVkrGdTFFGeYvd6gSA0b1snotsrCKuRVvNAREXjkChbxhWP5DUCouljttxnA5RLVUkcjwPVtwJ0ppFlDHFA6wujG4q3krwz2Tsn6vi/dTyV4Z7J2T9Xxfuq0ot14nc71ZavMq3krwz2Tsn6vi/dTyV4Z7J2T9Xxfuq0ol4nc71Yq8yreSvDPZOyfq+L91PJXhnsnZP1fF+6rSiXidzvVirzKt5K8M9k7J+r4v3VRsU4d4vUcVM8pJcetUtLTR24wU76OIsh5opC7lbrzeYgb6DelsSz3DifK/xFBOwIrZoden3KT8yXidzvVirzJnyV4Z7J2T9Xxfup5K8M9k7J+r4v3VaUS8Tud6sVeZVvJXhnsnZP1fF+6nkrwz2Tsn6vi/dVpRLxO53qxV5lW8leGeydk/V8X7qeSvDPZOyfq+L91WlEvE7nerFXmVbyV4Z7J2T9XxfuqZs+P2vHoHQ2q20ltieduZSQNiDj6yGgbUgixinTI1SKJteIqwiItJAiIgCIiAIiIAiIgCz3DgRxg4inl0DFbPO69fuUi0JZ5hrSOMPEY8pAMVr6nuP3KRAaGiIgCIiAIiIAiIgCIiAIiIAiIgCIiAIiIAs9w4DywcRTob7K2d29/wAFJ+b9CvF1bWutlYLa+CO4mF4pn1THPhbLynkL2tIJbza2AQdb0QvA3ycvlLcY+IHypLxi9wx3HLfUSzMZkZbR1INJDSczHdnuchrnF3KC7mHM5p1pAf0DREQBERAEREAREQBERAEREAREQHSvN5o8ftk9wuE7aakgbzPkcCfqAAHUknQAGySQACSsbvvGa/XWRzbPBBZaTubJUs7epcPXrfIz+Lz/AOMdy6fFHJJMjzCeja/dutDxDEwHo6oLfukh+sB3Zj1ak/2lVl9p7O9mS4ZamzlVvGj3L/d4boTDs8zF2v8AKqqafTy0dJr+uEr8+fWZe1lZ7pSfBUQi9u67P+lD5V0JaZL/AD6zL2srPdKT4KrFjt1XjeZX7K7bdZqTIL6Im3GuZS0pdOIxpvQxab9fKBzHqdnqpBEu2z/pQ+VdBaZL/PrMvays90pPgp8+sy9rKz3Sk+CqdleW0eHUdHU1sc8sdVXU9AwQNBIkmkEbCdkeaC4b9OvQVNKXfZm6e7h8q6C0yZZn+ZREEZPPKQd6mo6Yg/UeWNp/rVxxfjZPHOymyangiicdC50Yc2Nh9HaRkksHreHEDvIaASs1RaZ2wbNOhsuBLwSX4FrM9StcHAEEEHqCPSv1ZRwPyWRzKzG538zaNjaii2dlsBOnR/xMdrXqD2tGg0LV18DtWzxbLNilRcDIIiLlIEREAREQBERAeXKvm8dXwP32gutaH7Pp8Ik/q1rX1aXBXLizi0thyaW8RMJtl0c0yPHdDUgBuj6g8BpB/wBoO9Lm7zrILVWXeiZDQ3mrscrZA81FHHDI9zdEchEsb266g9BvoOvfv9N2edDOkQzIMcPXiiRbyTVT4tXC6WrhllFZZOfxrBb5n07oht7XBp85v1gbI+sL5NwnIAHA8Qb4djQJo7f0694/xb/+2u5ZsWvFtuMVRV5jdbtAzfNSVVNRsjfsEDZjga7oTvo4d3Xp0WyJxRpw2Wq8cMPUhiOJ4RTQQ0l6tGU47yvtVTPPTWiGZlRconQEEz89VJzFr3McXFuw4a2N6XYwywUWM0vBC82uDsLpdqdsFfP2ji6ra+3vk5ZCSeYB7GloP3ugBodFuVvwrHrTUVU9DYbZRT1QLaiSno443TA94eQ3bgfTtdpmP2uOK3RsttI2O3a8CY2BoFLppYOyGvM80lvm66HXcuSHY1DSlFTqugPLtFQY7dsFw7JqyeKqz2ryeh8YzT1B8KbN4aA+Es5vNawDQZrQDQdelesVBy4JjU90fcpMetUlxfI2V1Y+iiMzntIc1xfy72CAQd9CAop+E5A5xI4g3xoJ2Gijt+h/+ss5MqLZ64VrTd3cXXiwXFFTn4TkDnEjiDfGgnfKKO36H1f+GVqpY3UNBCyoqn1L4Yg2SqnDWukIHV7uUBoJ1s6AHqAC7IYnFvhpp1IXHhGXHiVThhPS21Jf16cvaQf99foK3xZnwXxOa3UtXfa2Iw1Fe1sdPG8acynbsgkHuL3EnXqDO47C0xfBe1ZsM3anY3LA2PIIiLxyBERAEREAREQHwrqGnudHNSVcEdTTTNLJIpWhzXtPeCCslv3A6sgldJj1yifATsUd0LjyfU2Zu3a/5muP+8thRdmzbXO2V1lRUrw4FMBPCfMm9PArY4+ktr3a/NuIf2L88lGZfgNt9/d8Nb+i9P41tOS0/sYZGAeSjMvwG2+/u+GnkozL8Btvv7vhrf0T41tOS0fUYZGAeSjMvwG2+/u+GnkozL8Btvv7vhrf0T41tOS0fUYZGBM4SZlKQBTWmL1ulr36H6Ijv+pXHE+C9PbqmKsvtW27VEZDmUscfJTMcDsEtJJeR6NnXp5d6K0xFone1tqnQ2a0XcK5BEReOQIiIAiIgP/Z",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "import uuid\n",
    "\n",
    "from langgraph.graph import StateGraph, MessagesState, START, END\n",
    "from langgraph.store.memory import InMemoryStore\n",
    "from langchain_core.messages import merge_message_runs\n",
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "from langchain_core.runnables.config import RunnableConfig\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.store.base import BaseStore\n",
    "\n",
    "# Initialize the model\n",
    "model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n",
    "\n",
    "# Memory schema\n",
    "class Memory(BaseModel):\n",
    "    content: str = Field(description=\"The main content of the memory. For example: User expressed interest in learning about French.\")\n",
    "\n",
    "# Create the Trustcall extractor\n",
    "trustcall_extractor = create_extractor(\n",
    "    model,\n",
    "    tools=[Memory],\n",
    "    tool_choice=\"Memory\",\n",
    "    # This allows the extractor to insert new memories\n",
    "    enable_inserts=True,\n",
    ")\n",
    "\n",
    "# Chatbot instruction\n",
    "MODEL_SYSTEM_MESSAGE = \"\"\"You are a helpful chatbot. You are designed to be a companion to a user. \n",
    "\n",
    "You have a long term memory which keeps track of information you learn about the user over time.\n",
    "\n",
    "Current Memory (may include updated memories from this conversation): \n",
    "\n",
    "{memory}\"\"\"\n",
    "\n",
    "# Trustcall instruction\n",
    "TRUSTCALL_INSTRUCTION = \"\"\"Reflect on following interaction. \n",
    "\n",
    "Use the provided tools to retain any necessary memories about the user. \n",
    "\n",
    "Use parallel tool calling to handle updates and insertions simultaneously:\"\"\"\n",
    "\n",
    "def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):\n",
    "\n",
    "    \"\"\"Load memories from the store and use them to personalize the chatbot's response.\"\"\"\n",
    "    \n",
    "    # Get the user ID from the config\n",
    "    user_id = config[\"configurable\"][\"user_id\"]\n",
    "\n",
    "    # Retrieve memory from the store\n",
    "    namespace = (\"memories\", user_id)\n",
    "    memories = store.search(namespace)\n",
    "\n",
    "    # Format the memories for the system prompt\n",
    "    info = \"\\n\".join(f\"- {mem.value['content']}\" for mem in memories)\n",
    "    system_msg = MODEL_SYSTEM_MESSAGE.format(memory=info)\n",
    "\n",
    "    # Respond using memory as well as the chat history\n",
    "    response = model.invoke([SystemMessage(content=system_msg)]+state[\"messages\"])\n",
    "\n",
    "    return {\"messages\": response}\n",
    "\n",
    "def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):\n",
    "\n",
    "    \"\"\"Reflect on the chat history and update the memory collection.\"\"\"\n",
    "    \n",
    "    # Get the user ID from the config\n",
    "    user_id = config[\"configurable\"][\"user_id\"]\n",
    "\n",
    "    # Define the namespace for the memories\n",
    "    namespace = (\"memories\", user_id)\n",
    "\n",
    "    # Retrieve the most recent memories for context\n",
    "    existing_items = store.search(namespace)\n",
    "\n",
    "    # Format the existing memories for the Trustcall extractor\n",
    "    tool_name = \"Memory\"\n",
    "    existing_memories = ([(existing_item.key, tool_name, existing_item.value)\n",
    "                          for existing_item in existing_items]\n",
    "                          if existing_items\n",
    "                          else None\n",
    "                        )\n",
    "\n",
    "    # Merge the chat history and the instruction\n",
    "    updated_messages=list(merge_message_runs(messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION)] + state[\"messages\"]))\n",
    "\n",
    "    # Invoke the extractor\n",
    "    result = trustcall_extractor.invoke({\"messages\": updated_messages, \n",
    "                                        \"existing\": existing_memories})\n",
    "\n",
    "    # Save the memories from Trustcall to the store\n",
    "    for r, rmeta in zip(result[\"responses\"], result[\"response_metadata\"]):\n",
    "        store.put(namespace,\n",
    "                  rmeta.get(\"json_doc_id\", str(uuid.uuid4())),\n",
    "                  r.model_dump(mode=\"json\"),\n",
    "            )\n",
    "\n",
    "# Define the graph\n",
    "builder = StateGraph(MessagesState)\n",
    "builder.add_node(\"call_model\", call_model)\n",
    "builder.add_node(\"write_memory\", write_memory)\n",
    "builder.add_edge(START, \"call_model\")\n",
    "builder.add_edge(\"call_model\", \"write_memory\")\n",
    "builder.add_edge(\"write_memory\", END)\n",
    "\n",
    "# Store for long-term (across-thread) memory\n",
    "across_thread_memory = InMemoryStore()\n",
    "\n",
    "# Checkpointer for short-term (within-thread) memory\n",
    "within_thread_memory = MemorySaver()\n",
    "\n",
    "# Compile the graph with the checkpointer fir and store\n",
    "graph = builder.compile(checkpointer=within_thread_memory, store=across_thread_memory)\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph(xray=1).draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Hi, my name is Lance\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Hi Lance! It's great to meet you. How can I assist you today?\n"
     ]
    }
   ],
   "source": [
    "# We supply a thread ID for short-term (within-thread) memory\n",
    "# We supply a user ID for long-term (across-thread) memory \n",
    "config = {\"configurable\": {\"thread_id\": \"1\", \"user_id\": \"1\"}}\n",
    "\n",
    "# User input \n",
    "input_messages = [HumanMessage(content=\"Hi, my name is Lance\")]\n",
    "\n",
    "# Run the graph\n",
    "for chunk in graph.stream({\"messages\": input_messages}, config, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "I like to bike around San Francisco\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "That sounds like a lot of fun! San Francisco has some beautiful routes for biking. Do you have a favorite trail or area you like to explore?\n"
     ]
    }
   ],
   "source": [
    "# User input \n",
    "input_messages = [HumanMessage(content=\"I like to bike around San Francisco\")]\n",
    "\n",
    "# Run the graph\n",
    "for chunk in graph.stream({\"messages\": input_messages}, config, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'value': {'content': \"User's name is Lance.\"}, 'key': 'dee65880-dd7d-4184-8ca1-1f7400f7596b', 'namespace': ['memories', '1'], 'created_at': '2024-10-30T22:18:52.413283+00:00', 'updated_at': '2024-10-30T22:18:52.413284+00:00'}\n",
      "{'value': {'content': 'User likes to bike around San Francisco.'}, 'key': '662195fc-8ea4-4f64-a6b6-6b86d9cb85c0', 'namespace': ['memories', '1'], 'created_at': '2024-10-30T22:18:56.597813+00:00', 'updated_at': '2024-10-30T22:18:56.597814+00:00'}\n"
     ]
    }
   ],
   "source": [
    "# Namespace for the memory to save\n",
    "user_id = \"1\"\n",
    "namespace = (\"memories\", user_id)\n",
    "memories = across_thread_memory.search(namespace)\n",
    "for m in memories:\n",
    "    print(m.dict())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "I also enjoy going to bakeries\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Biking and bakeries make a great combination! Do you have a favorite bakery in San Francisco, or are you on the hunt for new ones to try?\n"
     ]
    }
   ],
   "source": [
    "# User input \n",
    "input_messages = [HumanMessage(content=\"I also enjoy going to bakeries\")]\n",
    "\n",
    "# Run the graph\n",
    "for chunk in graph.stream({\"messages\": input_messages}, config, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Continue the conversation in a new thread."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "What bakeries do you recommend for me?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Since you enjoy biking around San Francisco, you might like to check out some of these bakeries that are both delicious and located in areas that are great for a bike ride:\n",
      "\n",
      "1. **Tartine Bakery** - Located in the Mission District, it's famous for its bread and pastries. The area is vibrant and perfect for a leisurely ride.\n",
      "\n",
      "2. **Arsicault Bakery** - Known for its incredible croissants, it's in the Richmond District, which offers a nice ride through Golden Gate Park.\n",
      "\n",
      "3. **B. Patisserie** - Situated in Lower Pacific Heights, this bakery is renowned for its kouign-amann and other French pastries. The neighborhood is charming and bike-friendly.\n",
      "\n",
      "4. **Mr. Holmes Bakehouse** - Famous for its cruffins, it's located in the Tenderloin, which is a bit more urban but still accessible by bike.\n",
      "\n",
      "5. **Noe Valley Bakery** - A cozy spot in Noe Valley, perfect for a stop after exploring the hilly streets of the area.\n",
      "\n",
      "Do any of these sound like a good fit for your next biking adventure?\n"
     ]
    }
   ],
   "source": [
    "# We supply a thread ID for short-term (within-thread) memory\n",
    "# We supply a user ID for long-term (across-thread) memory \n",
    "config = {\"configurable\": {\"thread_id\": \"2\", \"user_id\": \"1\"}}\n",
    "\n",
    "# User input \n",
    "input_messages = [HumanMessage(content=\"What bakeries do you recommend for me?\")]\n",
    "\n",
    "# Run the graph\n",
    "for chunk in graph.stream({\"messages\": input_messages}, config, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### LangSmith \n",
    "\n",
    "https://smith.langchain.com/public/c87543ec-b426-4a82-a3ab-94d01c01d9f4/r\n",
    "\n",
    "## Studio\n",
    "\n",
    "![Screenshot 2024-10-30 at 11.29.25 AM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/6732d0876d3daa19fef993ba_Screenshot%202024-11-11%20at%207.50.21%E2%80%AFPM.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
